Creating a Movie
Creating a movie involves several steps. You must first create and open the movie file that is to contain the movie. You then create the tracks and media structures for the movie. You then add samples to the media structures. Finally, you add the movie resource to the movie file. The sample program in this section,CreateWayCoolMovie
, demonstrates this process.This program has been divided into several segments. The main segment,
CreateMyCoolMovie
, creates and opens the movie file, then invokes other functions
to create the movie itself. Once the data has been added to the movie, this function saves the movie in its movie file and closes the file.The
CreateMyCoolMovie
function uses theCreateMyVideoTrack
andCreateMySoundTrack
functions to create the movie's tracks. TheCreateMyVideoTrack
function creates the video track and the media that contains the track's data. It then collects sample data in the media by calling theAddVideoSamplesToMedia
function. Note that this function uses the Image Compression Manager. TheCreateMySoundTrack
function creates the sound track and the media that contains the sound. It then collects sample data by calling theAddSoundSamplesToMedia
function.
- Note
- Throughout this volume, sound track refers to a QuickTime movie track that contains sound--as opposed to a soundtrack, which denotes the entire audio presentation of a movie as filmgoers know it. Consequently, a soundtrack may be made up of one or more QuickTime sound tracks.
![]()
A Sample Program for Creating a Movie
TheCreateWayCoolMovie
program consists of a number of segments, many of which are not included in this sample. Omitted segments deal with general initialization logic and other common aspects of Macintosh programming. TheHandleEditMenu
function, shown in Listing 2-5, has been included here to show how to initialize the Movie Toolbox with theEnterMovies
function.Listing 2-5 Creating a movie: The main program
#include <Types.h>#include <Traps.h>#include <Menus.h>#include <Packages.h>#include <Memory.h>#include <Errors.h>#include <Fonts.h>#include <QuickDraw.h>#include <Resources.h>#include <GestaltEqu.h>#include <FixMath.h>#include <Sound.h>#include <string.h> #include "Movies.h"#include "ImageCompression.h" void CheckError(OSErr error, Str255 displayString) { if (error == noErr) return; if (displayString[0] > 0) DebugStr(displayString); ExitToShell(); } void InitMovieToolbox (void) { OSErr err; InitGraf (&qd.thePort); InitFonts (); InitWindows (); InitMenus (); TEInit (); InitDialogs (nil); err = EnterMovies (); CheckError (err, "\pEnterMovies" ); } void main( void ) { InitMovieToolbox (); CreateMyCoolMovie (); }A Sample Function for Creating and Opening a Movie File
TheCreateMyCoolMovie
function, shown in Listing 2-6, contains the main logic for this program. This function creates and opens a movie file for the new movie. It then establishes a data reference for the movie's data (note that, if your movie's data is stored in the same file as the movie itself, you do not have to create a data reference--set the data reference to 0). This function then calls two other functions,CreateMyVideoTrack
andCreateMySoundTrack
, to create the tracks for the new movie. Once the tracks have been created,CreateMyCoolMovie
adds the new resource to the movie file and closes the movie file.Listing 2-6 Creating and opening a movie file
#define kMyCreatorType 'TVOD' /* Sample Player's creator type since it is the movie player of choice. You can use your own creator type, of course. */ #define kPrompt "\pEnter movie file name:" void CreateMyCoolMovie (void) { Point where = {100,100}; SFReply theSFReply; Movie theMovie = nil; FSSpec mySpec; short resRefNum = 0; short resId = 0; OSErr err = noErr; SFPutFile (where, "\pEnter movie file name:", "\pMovie File", nil, &theSFReply); if (!theSFReply.good) return; FSMakeFSSpec(theSFReply.vRefNum, 0, theSFReply.fName, &mySpec); err = CreateMovieFile (&mySpec, 'TVOD', smCurrentScript, createMovieFileDeleteCurFile, &resRefNum, &theMovie ); CheckError(err, "\pCreateMovieFile"); CreateMyVideoTrack (theMovie); CreateMySoundTrack (theMovie); err = AddMovieResource (theMovie, resRefNum, &resId, theSFReply.fName); CheckError(err, "\pAddMovieResource"); if (resRefNum) CloseMovieFile (resRefNum); DisposeMovie (theMovie); }A Sample Function for Creating a Video Track in a New Movie
TheCreateMyVideoTrack
function, shown in Listing 2-7, creates a video track in the new movie. This function creates the track and its media by calling theNewMovieTrack
andNewTrackMedia
functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. The bulk of this work is done by theAddVideoSamplesToMedia
subroutine. Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox'sInsertMediaIntoTrack
function (described on page 2-248).Listing 2-7 Creating a video track
#define kVideoTimeScale 600 #define kTrackStart 0 #define kMediaStart 0 #define kFix1 0x00010000 void CreateMyVideoTrack (Movie theMovie) { Track theTrack; Media theMedia; OSErr err = noErr; Rect trackFrame = {0,0,100,320}; theTrack = NewMovieTrack (theMovie, FixRatio(trackFrame.right,1), FixRatio(trackFrame.bottom,1), kNoVolume); CheckError( GetMoviesError(), "\pNewMovieTrack" ); theMedia = NewTrackMedia (theTrack, VideoMediaType, 600, // Video Time Scale nil, 0); CheckError( GetMoviesError(), "\pNewTrackMedia" ); err = BeginMediaEdits (theMedia); CheckError( err, "\pBeginMediaEdits" ); AddVideoSamplesToMedia (theMedia, &trackFrame); err = EndMediaEdits (theMedia); CheckError( err, "\pEndMediaEdits" ); err = InsertMediaIntoTrack (theTrack, 0,/* track start time */ 0, /* media start time */ GetMediaDuration (theMedia), kFix1); CheckError( err, "\pInsertMediaIntoTrack" ); }A Sample Function for Adding Video Samples to a Media
TheAddVideoSamplesToMedia
function, shown in Listing 2-8, creates video data frames, compresses each frame, and adds the frames to the media. This function creates its own video data by calling theDrawAFrame
function. Note that this function does not temporally compress the image sequence; rather, the function only spatially compresses each frame individually.Listing 2-8 Adding video samples to a media
#define kSampleDuration 240 /* video frames last 240 * 1/600th of a second */ #define kNumVideoFrames 29 #define kNoOffset 0 #define kMgrChoose 0 #define kSyncSample 0 #define kAddOneVideoSample 1 #define kPixelDepth 16 void AddVideoSamplesToMedia (Media theMedia, const Rect *trackFrame) { long maxCompressedSize; GWorldPtr theGWorld = nil; long curSample; Handle compressedData = nil; Ptr compressedDataPtr; ImageDescriptionHandle imageDesc = nil; CGrafPtr oldPort; GDHandle oldGDeviceH; OSErr err = noErr; err = NewGWorld (&theGWorld, 16, /* pixel depth */ trackFrame, nil, nil, (GWorldFlags) 0 ); CheckError (err, "\pNewGWorld"); LockPixels (theGWorld->portPixMap); err = GetMaxCompressionSize (theGWorld->portPixMap, trackFrame, 0, /* let ICM choose depth */ codecNormalQuality, 'rle ', (CompressorComponent) anyCodec, &maxCompressedSize); CheckError (err, "\pGetMaxCompressionSize" ); compressedData = NewHandle(maxCompressedSize); CheckError( MemError(), "\pNewHandle" ); MoveHHi( compressedData ); HLock( compressedData ); compressedDataPtr = StripAddress( *compressedData ); imageDesc = (ImageDescriptionHandle)NewHandle(4); CheckError( MemError(), "\pNewHandle" ); GetGWorld (&oldPort, &oldGDeviceH); SetGWorld (theGWorld, nil); for (curSample = 1; curSample < 30; curSample++) { EraseRect (trackFrame); DrawFrame(trackFrame, curSample); err = CompressImage (theGWorld->portPixMap, trackFrame, codecNormalQuality, 'rle ', imageDesc, compressedDataPtr ); CheckError( err, "\pCompressImage" ); err = AddMediaSample(theMedia, compressedData, 0, /* no offset in data */ (**imageDesc).dataSize, 60, /* frame duration = 1/10 sec */ (SampleDescriptionHandle)imageDesc, 1, /* one sample */ 0, /* self-contained samples */ nil); CheckError( err, "\pAddMediaSample" ); } SetGWorld (oldPort, oldGDeviceH); if (imageDesc) DisposeHandle ((Handle)imageDesc); if (compressedData) DisposeHandle (compressedData); if (theGWorld) DisposeGWorld (theGWorld); }A Sample Function for Creating Video Data for a Movie
TheDrawAFrame
function, shown in Listing 2-9, creates video data for this movie. This function draws a different frame each time it is invoked, based on the sample number, which is passed as a parameter.Listing 2-9 Creating video data
void DrawFrame (const Rect *trackFrame, long curSample) { Str255 numStr; ForeColor( redColor ); PaintRect( trackFrame ); ForeColor( blueColor ); NumToString (curSample, numStr); MoveTo ( trackFrame->right / 2, trackFrame->bottom / 2); TextSize ( trackFrame->bottom / 3); DrawString (numStr); }A Sample Function for Creating a Sound Track
TheCreateMySoundTrack
function, shown in Listing 2-10, creates the movie's sound track. This sound track is not synchronized to the video frames of the movie--rather, it is just a separate sound track that accompanies the video data. This function relies upon an'snd '
resource for its source sound. TheCreateMySoundTrack
function uses theCreateSoundDescription
function to create the sound description structure for these samples.As with the
CreateMyVideoTrack
function discussed earlier, this function creates the track and its media by calling theNewMovieTrack
andNewTrackMedia
functions, respectively. This function then establishes a media-editing session and adds the movie's data to the media. This function adds the sound samples using a single invocation of theAddMediaSample
function. This is possible because all the sound samples are the same size and rely on the same sample description (theSoundDescription
structure). If you use this approach, it is often advisable to break up the sound data in the movie, so that the movie plays smoothly. After you create the movie, you can call theFlattenMovie
function (described on page 2-93) to create an interleaved version of the movie. Another approach is to callAddMediaSample
multiple times, breaking the sound into multiple chunks at that point.Once the data has been added to the media, this function adds the media to the track by calling the Movie Toolbox's
InsertMediaIntoTrack
function (described on page 2-248).Listing 2-10 Creating a sound track
#define kSoundSampleDuration 1 #define kSyncSample 0 #define kTrackStart 0 #define kMediaStart 0 #define kFix1 0x00010000 void CreateMySoundTrack (Movie theMovie) { Track theTrack; Media theMedia; Handle sndHandle = nil; SoundDescriptionHandle sndDesc = nil; long sndDataOffset; long sndDataSize; long numSamples; OSErr err = noErr; sndHandle = GetResource ('snd ', 128); CheckError (ResError(), "\pGetResource" ); if (sndHandle == nil) return; sndDesc = (SoundDescriptionHandle) NewHandle(4); CheckError (MemError(), "\pNewHandle" ); CreateSoundDescription (sndHandle, sndDesc, &sndDataOffset, &numSamples, &sndDataSize ); theTrack = NewMovieTrack (theMovie, 0, 0, kFullVolume); CheckError (GetMoviesError(), "\pNewMovieTrack" ); theMedia = NewTrackMedia (theTrack, SoundMediaType, FixRound ((**sndDesc).sampleRate), nil, 0); CheckError (GetMoviesError(), "\pNewTrackMedia" ); err = BeginMediaEdits (theMedia); CheckError( err, "\pBeginMediaEdits" ); err = AddMediaSample(theMedia, sndHandle, sndDataOffset, /* offset in data */ sndDataSize, 1, /* duration of each sound sample */ (SampleDescriptionHandle) sndDesc, numSamples, 0, /* self-contained samples */ nil); CheckError( err, "\pAddMediaSample" ); err = EndMediaEdits (theMedia); CheckError( err, "\pEndMediaEdits" ); err = InsertMediaIntoTrack (theTrack, 0, /* track start time */ 0, /* media start time */ GetMediaDuration (theMedia), kFix1); CheckError( err, "\pInsertMediaIntoTrack" ); if (sndDesc != nil) DisposeHandle( (Handle)sndDesc); }A Sample Function for Creating a Sound Description Structure
TheCreateSoundDescription
function, shown in Listing 2-11, creates a sound description structure that correctly describes the sound samples obtained from the'snd '
resource. This function can handle all the sound data formats that are possible in the sound resource. This function uses theGetSndHdrOffset
function to locate the sound data in the sound resource.Listing 2-11 Creating a sound description
/* Constant definitions */ /* for the following constants, please consult the Macintosh Audio Compression and Expansion Toolkit */ #define kMACEBeginningNumberOfBytes 6 #define kMACE31MonoPacketSize 2 #define kMACE31StereoPacketSize 4 #define kMACE61MonoPacketSize 1 #define kMACE61StereoPacketSize 2 void CreateSoundDescription (Handle sndHandle, SoundDescriptionHandlesndDesc, long *sndDataOffset, long *numSamples, long *sndDataSize ) { long sndHdrOffset = 0; long sampleDataOffset; SoundHeaderPtr sndHdrPtr = nil; long numFrames; long samplesPerFrame; long bytesPerFrame; SignedByte sndHState; SoundDescriptionPtr sndDescPtr; *sndDataOffset = 0; *numSamples = 0; *sndDataSize = 0; SetHandleSize( (Handle)sndDesc, sizeof(SoundDescription) ); CheckError(MemError(),"\pSetHandleSize"); sndHdrOffset = GetSndHdrOffset (sndHandle); if (sndHdrOffset == 0) CheckError(-1, "\pGetSndHdrOffset "); /* we can use pointers since we don't move memory */ sndHdrPtr = (SoundHeaderPtr) (*sndHandle + sndHdrOffset); sndDescPtr = *sndDesc; sndDescPtr->descSize = sizeof (SoundDescription); /* total size of sound description structure */ sndDescPtr->resvd1 = 0; sndDescPtr->resvd2 = 0; sndDescPtr->dataRefIndex = 1; sndDescPtr->compressionID = 0; sndDescPtr->packetSize = 0; sndDescPtr->version = 0; sndDescPtr->revlevel = 0; sndDescPtr->vendor = 0; switch (sndHdrPtr->encode) { case stdSH: sndDescPtr->dataFormat = 'raw '; /* uncompressed offset-binary data */ sndDescPtr->numChannels = 1; /* number of channels of sound */ sndDescPtr->sampleSize = 8; /* number of bits per sample */ sndDescPtr->sampleRate = sndHdrPtr->sampleRate; /* sample rate */ *numSamples = sndHdrPtr->length; *sndDataSize = *numSamples; bytesPerFrame = 1; samplesPerFrame = 1; sampleDataOffset = (Ptr)&sndHdrPtr->sampleArea - (Ptr)sndHdrPtr; break; case extSH: { ExtSoundHeaderPtr extSndHdrP; extSndHdrP = (ExtSoundHeaderPtr)sndHdrPtr; sndDescPtr->dataFormat = 'raw '; /* uncompressed offset-binary data */ sndDescPtr->numChannels = extSndHdrP->numChannels; /* number of channels of sound */ sndDescPtr->sampleSize = extSndHdrP->sampleSize; /* number of bits per sample */ sndDescPtr->sampleRate = extSndHdrP->sampleRate; /* sample rate */ numFrames = extSndHdrP->numFrames; *numSamples = numFrames; bytesPerFrame = extSndHdrP->numChannels * ( extSndHdrP->sampleSize / 8); samplesPerFrame = 1; *sndDataSize = numFrames * bytesPerFrame; sampleDataOffset = (Ptr)(&extSndHdrP->sampleArea) - (Ptr)extSndHdrP; } break; case cmpSH: { CmpSoundHeaderPtr cmpSndHdrP; cmpSndHdrP = (CmpSoundHeaderPtr)sndHdrPtr; sndDescPtr->numChannels = cmpSndHdrP->numChannels; /* number of channels of sound */ sndDescPtr->sampleSize = cmpSndHdrP->sampleSize; /* number of bits per sample before compression */ sndDescPtr->sampleRate = cmpSndHdrP->sampleRate; /* sample rate */ numFrames = cmpSndHdrP->numFrames; sampleDataOffset =(Ptr)(&cmpSndHdrP->sampleArea) - (Ptr)cmpSndHdrP; switch (cmpSndHdrP->compressionID) { case threeToOne: sndDescPtr->dataFormat = 'MAC3'; /* compressed 3:1 data */ samplesPerFrame = kMACEBeginningNumberOfBytes; *numSamples = numFrames * samplesPerFrame; switch (cmpSndHdrP->numChannels) { case 1: bytesPerFrame = cmpSndHdrP->numChannels * kMACE31MonoPacketSize; break; case 2: bytesPerFrame = cmpSndHdrP->numChannels * kMACE31StereoPacketSize; break; default: CheckError(-1, "\pCorrupt sound data" ); break; } *sndDataSize = numFrames * bytesPerFrame; break; case sixToOne: sndDescPtr->dataFormat = 'MAC6'; /* compressed 6:1 data */ samplesPerFrame = kMACEBeginningNumberOfBytes; *numSamples = numFrames * samplesPerFrame; switch (cmpSndHdrP->numChannels) { case 1: bytesPerFrame = cmpSndHdrP->numChannels * kMACE61MonoPacketSize; break; case 2: bytesPerFrame = cmpSndHdrP->numChannels * kMACE61StereoPacketSize; break; default: CheckError(-1, "\pCorrupt sound data" ); break; } *sndDataSize = (*numSamples) * bytesPerFrame; break; default: CheckError(-1, "\pCorrupt sound data" ); break; } } /* switch cmpSndHdrP->compressionID:*/ break; /* of cmpSH: */ default: CheckError(-1, "\pCorrupt sound data" ); break; } /* switch sndHdrPtr->encode */ *sndDataOffset = sndHdrOffset + sampleDataOffset; }Parsing a Sound Resource
TheGetSndHdrOffset
function, shown in Listing 2-12, parses the specified sound resource and locates the sound data stored in the resource. TheGetSndHdrOffset
function cruises through a specified'snd '
resource. It locates the sound data, if any, and returns its type, offset, and size into the resource.The
GetSndHdrOffset
function returns an offset instead of a pointer so that the data is not locked in memory. By returning an offset, the calling function can decide when and if it wants the resource locked down to access the sound data.The first step in finding this data is to determine if the
'snd '
resource is format (type) 1 or format (type) 2. A type 2 is easy, but a type 1 requires that you find the number of'snth'
resource types specified and then skip over each one, including theinit
option. Once you do this, you have a pointer to the number of commands in the'snd '
resource. When the function finds the first one, it examines the command to find out if it is a sound data command. Since it is a sound resource, the command also has itsdataPointerFlag
parameter set to 1. When the function finds a sound data command, it returns its offset and type, and exits.
Listing 2-12 Parsing a sound resource
- WARNING
- Do not send the
GetSndHdrOffset
function anil
handle; if you do, your system will crash.![]()
typedef SndCommand *SndCmdPtr; typedef struct { short format; short numSynths; } Snd1Header, *Snd1HdrPtr, **Snd1HdrHndl; typedef struct { short format; short refCount; } Snd2Header, *Snd2HdrPtr, **Snd2HdrHndl; typedef struct { short synthID; long initOption; } SynthInfo, *SynthInfoPtr; long GetSndHdrOffset (Handle sndHandle) { short howManyCmds; long sndOffset = 0; Ptr sndPtr; if (sndHandle == nil) return 0; sndPtr = *sndHandle; if (sndPtr == nil) return 0; if ((*(Snd1HdrPtr)sndPtr).format == firstSoundFormat) { short synths = ((Snd1HdrPtr)sndPtr)->numSynths; sndPtr += sizeof(Snd1Header) + (sizeof(SynthInfo) * synths); } else { sndPtr += sizeof(Snd2Header); } howManyCmds = *(short *)sndPtr; sndPtr += sizeof(howManyCmds); /* sndPtr is now at the first sound command--cruise all commands and find the first soundCmd or bufferCmd */ while (howManyCmds > 0) { switch (((SndCmdPtr)sndPtr)->cmd) { case (soundCmd + dataOffsetFlag): case (bufferCmd + dataOffsetFlag): sndOffset = ((SndCmdPtr)sndPtr)->param2; howManyCmds = 0;/* done, get out of loop */ break; default: /* catch any other type of commands */ sndPtr += sizeof(SndCommand); howManyCmds--; break; } } /* done with all commands */ return sndOffset; } /* of GetSndHdrOffset */